package com.qualityeclipse.genealogy.editor;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.EventObject;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.dnd.TemplateTransferDragSourceListener;
import org.eclipse.gef.dnd.TemplateTransferDropTargetListener;
import org.eclipse.gef.editparts.ScalableFreeformRootEditPart;
import org.eclipse.gef.palette.PaletteRoot;
import org.eclipse.gef.ui.palette.PaletteViewer;
import org.eclipse.gef.ui.palette.PaletteViewerProvider;
import org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette;
import org.eclipse.gef.ui.parts.GraphicalViewerKeyHandler;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.part.FileEditorInput;

import com.qualityeclipse.genealogy.model.GenealogyGraph;
import com.qualityeclipse.genealogy.model.io.GenealogyGraphReader;
import com.qualityeclipse.genealogy.model.io.GenealogyGraphWriter;
import com.qualityeclipse.genealogy.parts.GenealogyEditPartFactory;

/**
 * An editor for loading, modifying, and saving genealogy graphs
 */
public class GenealogyGraphEditor extends GraphicalEditorWithFlyoutPalette
{
    public static final String ID = "com.qualityeclipse.genealogy.editor";

    private final GenealogyGraph genealogyGraph = new GenealogyGraph();

    public GenealogyGraphEditor() {
        setEditDomain(new DefaultEditDomain(this));
    }

    /**
     * Configure the viewer to display a genealogy graph
     */
    @Override
    protected void configureGraphicalViewer() {
        super.configureGraphicalViewer();
        final GraphicalViewer viewer = getGraphicalViewer();
        viewer.setEditPartFactory(new GenealogyEditPartFactory());
        viewer.setRootEditPart(new ScalableFreeformRootEditPart());

        // Two different approaches for dynamically modifying the selection
        // to exclude nested figures when that figure's ancestor is selected

        // Option #1
        //viewer.addSelectionChangedListener(new SelectionModificationChangeListener(viewer));

        // Option #2
        viewer.setSelectionManager(new ModifiedSelectionManager(viewer));

        viewer.setKeyHandler(new GraphicalViewerKeyHandler(viewer));
    }

    /**
     * Initialize the viewer to display the specific genealogy graph being edited
     */
    @Override
    protected void initializeGraphicalViewer() {
        super.initializeGraphicalViewer();
        getGraphicalViewer().setContents(genealogyGraph);
        getGraphicalViewer().addDropTargetListener(
            new TemplateTransferDropTargetListener(getGraphicalViewer()));
    }

    /**
     * Read the content from the specified file.
     */
    @Override
    protected void setInput(IEditorInput input) {
        super.setInput(input);
//        IFile file = ((IFileEditorInput) input).getFile();
        setPartName("test.xml");

        // For the purposes of this book, if the file is empty
        // then load some default content into the model

        try {
//            InputStream stream = file.getContents();
//            boolean isEmpty = stream.read() == -1;
//            stream.close();
//            if (isEmpty) {
                readAndClose(getClass().getResourceAsStream("../view/genealogy.xml"));
                return;
//            }
        }
        catch (Exception e) {
            e.printStackTrace();
            return;
        }

        // Read the content from the stream into the model

//        try {
//            readAndClose(file.getContents());
//        }
//        catch (CoreException e) {
//            handleException(e);
//            return;
//        }
    }

    /**
     * Read the content from the stream into the model
     *
     * @param stream the stream to be read and closed (not <code>null</code>)
     */
    private void readAndClose(InputStream stream) {
        genealogyGraph.clear();
        try {
            new GenealogyGraphReader(genealogyGraph).read(stream);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            try {
                stream.close();
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Save the model being edited into the file associated with this editor,
     * then update the editor state to indicate that the contents have been saved.
     *
     * @param monitor the progress monitor
     */
    @Override
    public void doSave(IProgressMonitor monitor) {

        // Serialize the model

        StringWriter writer = new StringWriter(5000);
        new GenealogyGraphWriter(genealogyGraph).write(new PrintWriter(writer));
        ByteArrayInputStream stream = new ByteArrayInputStream(writer.toString().getBytes());

        // Store the serialized model in the file

        IFile file = ((IFileEditorInput) getEditorInput()).getFile();
        try {
            if (file.exists()) {
                file.setContents(stream, false, true, monitor);
            } else {
                file.create(stream, false, monitor);
            }
        }
        catch (CoreException e) {
            handleException(e);
            return;
        }

        // Update the editor state to indicate that the contents
        // have been saved and notify all listeners about the change in state

        getCommandStack().markSaveLocation();
        firePropertyChange(PROP_DIRTY);
    }

    /**
     * Log the specified exception and notify the user that an problem has occurred
     *
     * @param ex the exception that occurred (not <code>null</code>)
     */
    private void handleException(Exception ex) {
        ex.printStackTrace();
        Status status = new Status(
            IStatus.ERROR,
            "com.qualityeclipse.genealogy",
            "An exception occurred while saving the file",
            ex);
        ErrorDialog.openError(
            getSite().getShell(), "Exception", ex.getMessage(), status);
    }

    /**
     * Override the superclass implementation to return <code>true</code>
     * indicating that the doSaveAs() method may be called
     * to save the editor's content in a different file.
     */
    @Override
    public boolean isSaveAsAllowed() {
        return true;
    }

    /**
     * Prompt the user for a new file in which to save the editor content.
     * If the user chooses a new file, then change the file associated with the editor
     * to the file selected by the user and call the doSave() method
     * to save the editor content in the new file.
     */
    @Override
    public void doSaveAs() {

        // Prompt the user for a new file

        SaveAsDialog dialog = new SaveAsDialog(getSite().getShell());
        dialog.setOriginalFile(((IFileEditorInput) getEditorInput()).getFile());
        dialog.open();

        // If the user clicked cancel, then bail out

        IPath path = dialog.getResult();
        if (path == null) {
            return;
        }

        // Change the file associated with this editor and call doSave(...)
        // to save the editor's content in the new file

        IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
        super.setInput(new FileEditorInput(file));
        doSave(null);
        setPartName(file.getName());
        firePropertyChange(PROP_INPUT);
    }

    @Override
    public void commandStackChanged(EventObject event) {
        firePropertyChange(IEditorPart.PROP_DIRTY);
        super.commandStackChanged(event);
    }

    /**
     * If possible, adapt the receiver to an object of the specified type.
     */
    @Override
    @SuppressWarnings("rawtypes")
    public Object getAdapter(Class type) {
        Object value = super.getAdapter(type);

        /* For informational purposes,
         * echo the calls to getAdapter(...) and the results returned
         */
        System.out.println("getAdapter(" + type.getSimpleName() + ") -> "
            + (value != null ? value.getClass().getSimpleName() : "null"));

        return value;
    }

    /**
     * Create and answer the palette for use with the receiver
     */
    @Override
    protected PaletteRoot getPaletteRoot() {
        return GenealogyGraphEditorPaletteFactory.createPalette();
    }

    /**
     * Override the superclass method to return a provider
     * that adds a creation tool drag listener
     */
    @Override
    protected PaletteViewerProvider createPaletteViewerProvider() {
        return new PaletteViewerProvider(getEditDomain()) {
            @Override
            protected void configurePaletteViewer(PaletteViewer viewer) {
                super.configurePaletteViewer(viewer);
                viewer.addDragSourceListener(new TemplateTransferDragSourceListener(viewer));
            }
        };
    }
}
